一、前言:
隨著遊戲功能越來越豐富,玩家隨時需要一個能夠 「暫停遊戲、調整設定」 的通用介面,同時也需要一套能跨場景動態控制常駐按鈕(如選單按鈕) 顯隱的機制。今天我們要邁入遊戲架構的重要里程碑——實裝全域暫停選單總成與跨場景常駐選單管理器!我們將透過 PauseMenuController.cs 與 PersistentMenuController.cs 解決時間軸凍結(Time.timeScale)、背景對話偷跑防護,以及遊戲內常駐按鈕的場景白名單過濾。
二、 系統選單與常駐 UI 架構需求
在 2D 敘事與經營遊戲中,選單與暫停系統需要兼顧全域控制與場景適應性:
三、 核心程式實作:PauseMenuController.cs 與 PersistentMenuController 協作架構
為了讓常駐物件完美運作,我們在 DontDestroyOnLoad 下的同一個管理物件中同時掛載了兩個核心腳本:負責管理常駐按鈕顯隱的 PersistentMenuController,以及負責暫停與時間軸控制的 PauseMenuController。
1. PauseMenuController.cs(暫停與時間凍結核心):
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class PauseMenuController : MonoBehaviour {
[Header("UI 連結設定")]
public GameObject pauseMenuPanel; // 暫停選單面板
public Button pauseButton; // 畫面上的暫停按鈕
public Button resumeButton; // 繼續遊戲按鈕
public Button mainMenuButton; // 返回主選單按鈕
// 全域靜態變數,供其他腳本(如對話系統)檢查遊戲是否暫停
public static bool IsGamePaused = false;
void Start() {
if (pauseMenuPanel != null)
pauseMenuPanel.SetActive(false); // 遊戲開始時預設隱藏
if (pauseButton != null)
pauseButton.onClick.AddListener(TogglePause);
if (resumeButton != null)
resumeButton.onClick.AddListener(ResumeGame);
if (mainMenuButton != null)
mainMenuButton.onClick.AddListener(GoToMainMenu);
}
void Update() {
DetectEscapeKey();
}
private void DetectEscapeKey() {
#if ENABLE_INPUT_SYSTEM
if (UnityEngine.InputSystem.Keyboard.current != null &&
UnityEngine.InputSystem.Keyboard.current.escapeKey.wasPressedThisFrame) {
TogglePause();
}
#else
if (Input.GetKeyDown(KeyCode.Escape)) {
TogglePause();
}
#endif
}
public void TogglePause() {
if (IsGamePaused) ResumeGame();
else PauseGame();
}
public void PauseGame() {
if (pauseMenuPanel != null) pauseMenuPanel.SetActive(true);
Time.timeScale = 0f; // 凍結時間軸
IsGamePaused = true;
Debug.Log("遊戲已暫停");
}
public void ResumeGame() {
if (pauseMenuPanel != null) pauseMenuPanel.SetActive(false);
Time.timeScale = 1f; // 恢復時間軸
IsGamePaused = false;
Debug.Log("遊戲已恢復");
}
public void GoToMainMenu() {
// **關鍵防呆**:離開場景前務必將時間軸恢復,否則下個場景會直接卡死!
Time.timeScale = 1f;
IsGamePaused = false;
SceneManager.LoadScene("SampleScene"); // 替換為你的主選單場景名稱
}
}
2. PersistentMenuController.cs(負責管理常駐按鈕顯隱):
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class PersistentMenuController : MonoBehaviour {
[Header("UI 容器設定")]
public GameObject inGameButtonsContainer; // 遊戲中的常駐按鈕容器 (例如暫停、存檔按鈕)
[Header("黑名單設定 (不顯示常駐按鈕的場景)")]
public List<string> hideInScenes = new List<string> { "TitleScene", "MainMenuScene" };
void OnEnable() {
// 註冊場景切換事件
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDisable() {
// 取消註冊場景切換事件,避免記憶體洩漏
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode) {
CheckButtonVisibility(scene.name);
}
private void CheckButtonVisibility(string sceneName) {
if (inGameButtonsContainer == null) return;
// 如果當前場景在黑名單內,就隱藏常駐按鈕;否則顯示
bool shouldHide = hideInScenes.Contains(sceneName);
inGameButtonsContainer.SetActive(!shouldHide);
// 離開某些特定場景時,順便確保時間軸是正常運行的(防止卡死)
if (!shouldHide && Time.timeScale == 0f) {
Time.timeScale = 1f;
}
}
}
四、 Unity Inspector 設定與實測驗收
在 Unity 介面中將元件進行配置與串接:
1. 常駐總成物件配置 (PersistentManagers)



*圖三、到下一幕點擊Setting即顯現暫停鍵8
五、 開發實戰經驗:UI 防錯與時間軸設計
1. Time.timeScale = 0f 的背景防禦(Early Return): 時間暫停雖然能凍結物理和動畫,但無法自動阻止寫在 Update() 裡的點擊事件。記得在對話管理器或重要點擊腳本中加入檢查,這樣才能徹底杜絕暫停時背景程式偷偷運作的隱患。
if (PauseMenuController.IsGamePaused) return;
2. 場景切換的時間軸還原機制: 在任何會呼叫 SceneManager.LoadScene() 的按鈕或轉場邏輯中,第一行絕對要寫 Time.timeScale = 1f。這是無數開發者在實作暫停功能時最容易踩的經典大坑。
3. 善用 PersistentMenuController 管理非同步場景 UI: 將遊戲內常用按鈕包裝成 InGameButtons 並交由白名單過濾,能大幅減少各個場景需要手動開關 UI 的重複程式碼。
六、 今日成果與小結
最後,別忘了上傳紀錄!DAY 20 的內容是實作 PauseMenuController(暫停與時間軸凍結核心) 與 PersistentMenuController(跨場景常駐按鈕動態顯隱機制),並且完美收尾了整個暫停選單與常駐 UI 的協作架構。因為這依舊是延續前幾天(Day 19)建立的常駐管理器與 UI 總成架構,所以它完全屬於同一個獨立分支 feature/persistent-managers:
1.檢查修改狀態
git status
2.加入暫存區並提交 Commit
git add .
git commit -m "feat: 實作 PauseMenuController 暫停與時間軸凍結機制,並結合 PersistentMenuController 完成常駐按鈕場景白名單過濾"
3.推送到遠端開發分支
git push origin feature/persistent-managers